iT邦幫忙

2026 iThome 鐵人賽

0
AI Engineering

Java × AI:30天打造智慧健康助理系列 第 16 篇

建立飲水量歷史查詢 API

  • 分享至 

  • xImage
  •  

Day 16|建立飲水量歷史查詢 API

昨天的 Day 15,我們終於成功讓 Java 程式透過 JdbcTemplate,將飲水紀錄儲存到 H2 Database。

現在就算重新啟動 Spring Boot,資料也不會像之前使用 List 時一樣直接消失。

不過我發現,目前的飲水紀錄功能還有一個限制。

我們只能查詢今天的飲水量,卻沒辦法查看昨天或前幾天的紀錄!

既然資料都已經存進資料庫了,如果只能查詢今天,好像有點可惜 XD

所以今天決定繼續升級飲水紀錄功能!

除了查詢當日飲水量,也要讓使用者可以指定日期,甚至一次查看最近七天的飲水紀錄。

今天的目標是:

使用 Spring Boot、LocalDate 和 SQL,建立飲水量歷史查詢 API。


一、今天要完成哪些功能?

目前我們已經有兩個 API:

HTTP Method API 功能
POST /health/water 新增飲水紀錄
GET /health/water 查詢今日飲水總量

今天準備再增加兩個:

HTTP Method API 功能
GET /health/water/date?date=2026-10-17 查詢指定日期的飲水量
GET /health/water/history 查詢最近七天的飲水紀錄

例如使用者想知道某一天喝了多少水,可以發送:

GET /health/water/date?date=2026-10-17

系統就會回傳:

{
  "date": "2026-10-17",
  "totalWater": 1500,
  "unit": "ml"
}

如果想查看最近七天的紀錄,就可以發送:

GET /health/water/history

回傳一組包含日期和飲水量的 JSON 陣列。

這樣就能開始觀察不同日期的飲水情況。


二、先認識 LocalDate

在 Day 13,我們已經簡單使用過:

LocalDate

它是 Java 提供的日期類別。

例如:

LocalDate today = LocalDate.now();

代表取得今天的日期。

如果想取得昨天:

LocalDate yesterday = LocalDate.now().minusDays(1);

如果想取得七天前:

LocalDate sevenDaysAgo = LocalDate.now().minusDays(7);

也可以直接指定日期:

LocalDate date = LocalDate.of(2026, 10, 17);

這樣就能建立:

2026-10-17

今天我們會利用 LocalDate,讓使用者查詢不同日期的飲水紀錄。


三、認識 @RequestParam

其實我們在 Day 6 就已經接觸過:

@RequestParam

當時是讓使用者透過網址傳送參數。

例如:

/hello?name=Hannie

今天則要利用相同的概念接收日期。

例如:

/health/water/date?date=2026-10-17

其中:

date=2026-10-17

就是 Query Parameter。

在 Spring Boot 裡面可以這樣接收:

@GetMapping("/date")
public String getWaterByDate(
        @RequestParam String date) {

    return date;
}

如果使用者輸入:

?date=2026-10-17

就可以取得:

2026-10-17

不過今天我們不想只把日期當成一般字串。

而是希望 Spring Boot 直接將它轉換成:

LocalDate

四、使用 @DateTimeFormat

為了讓 Spring Boot 知道日期的格式,可以使用:

@DateTimeFormat

例如:

@RequestParam
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
LocalDate date

這樣就可以接收:

2026-10-17

這種 ISO 日期格式。

如果使用者輸入不符合格式的日期,例如:

2026/10/17

Spring Boot 就可能回傳 HTTP 400,表示參數格式不正確。

這樣就不需要自己使用字串切割來處理日期。


五、修改 WaterRepository

接下來正式開始修改程式。

首先打開:

WaterRepository.java

昨天我們已經建立:

getTotalWater(LocalDate date)

可以查詢指定日期的飲水總量。

所以今天第一個功能,其實已經有一部分完成了!

不過如果要查詢最近七天的紀錄,我們還需要新增一個 Method。

今天會使用:

query()

來取得多筆資料。


六、認識 JdbcTemplate.query()

昨天我們使用:

queryForObject()

取得單一結果。

例如:

Integer total = jdbcTemplate.queryForObject(
    sql,
    Integer.class,
    date
);

但今天要查詢最近七天的資料,結果可能不只一筆。

這時候就可以使用:

query()

例如:

jdbcTemplate.query(sql, rowMapper, startDate, endDate);

其中 rowMapper 負責將資料庫查詢結果轉換成 Java 物件。

例如資料庫回傳:

2026-10-15 | 1200
2026-10-16 | 1500
2026-10-17 | 1800

我們就可以把這些資料整理成 Java 的 List。

不過這次的 List 和 Day 13 不太一樣。

Day 13 是使用 List 儲存資料,今天則是使用 List 接收資料庫查詢結果。

真正的資料仍然保存在 H2 Database 裡面。


七、建立 WaterHistoryResponse DTO

為了讓回傳資料更清楚,今天先建立一個新的 DTO。

檔案名稱:

WaterHistoryResponse.java

程式如下:

package com.example.healthassistant.dto;

import java.time.LocalDate;

public class WaterHistoryResponse {

    private LocalDate date;
    private int totalWater;

    public WaterHistoryResponse(LocalDate date, int totalWater) {
        this.date = date;
        this.totalWater = totalWater;
    }

    public LocalDate getDate() {
        return date;
    }

    public int getTotalWater() {
        return totalWater;
    }
}

這個 DTO 主要用來表示:

日期
飲水總量

例如:

{
  "date": "2026-10-17",
  "totalWater": 1500
}

之前我們使用 Map 回傳資料。

但如果回傳內容有固定結構,也可以建立專門的 DTO。

這樣比較容易知道每個欄位代表什麼。


八、在 Repository 新增歷史查詢

接下來回到:

WaterRepository.java

加入:

public List<WaterHistoryResponse> getWaterHistory(
        LocalDate startDate,
        LocalDate endDate) {

    String sql = """
        SELECT record_date, SUM(amount) AS total_water
        FROM water_records
        WHERE record_date BETWEEN ? AND ?
        GROUP BY record_date
        ORDER BY record_date
        """;

    return jdbcTemplate.query(
        sql,
        (rs, rowNum) -> new WaterHistoryResponse(
            rs.getDate("record_date").toLocalDate(),
            rs.getInt("total_water")
        ),
        startDate,
        endDate
    );
}

記得加入:

import java.util.List;
import com.example.healthassistant.dto.WaterHistoryResponse;

這段程式看起來比較複雜,但其實可以拆開理解。

1. BETWEEN

WHERE record_date BETWEEN ? AND ?

代表查詢某個日期範圍內的資料。

例如:

2026-10-11 ~ 2026-10-17

2. GROUP BY

GROUP BY record_date

代表依照日期分組。

因為同一天可能會有很多筆飲水紀錄。

例如:

10/17:300 ml
10/17:500 ml
10/17:250 ml

我們希望把同一天的資料合併。

3. SUM

SUM(amount)

代表計算同一天的飲水總量。

因此:

300 + 500 + 250

就會得到:

1050 ml

4. ORDER BY

ORDER BY record_date

代表依照日期排序。

這樣回傳的紀錄就會按照日期由舊到新排列。


九、修改 WaterService

Repository 完成後,接下來要修改 Service。

在:

WaterService.java

加入:

public int getTotalWaterByDate(LocalDate date) {

    return waterRepository.getTotalWater(date);
}

這個 Method 負責查詢指定日期的飲水總量。

接著加入:

public List<WaterHistoryResponse> getWaterHistory() {

    LocalDate today = LocalDate.now();

    LocalDate startDate = today.minusDays(6);

    return waterRepository.getWaterHistory(startDate, today);
}

這裡有一個需要注意的地方。

為什麼是:

minusDays(6)

而不是:

minusDays(7)

因為我們希望查詢的是:

包含今天在內的最近七個日曆日。

例如今天是:

10/17

就要查詢:

10/11 ~ 10/17

總共七天。

如果使用 minusDays(7),再加上今天,就會變成八天。


十、完整的 WaterService

整理之後,WaterService.java 可以改成:

package com.example.healthassistant.service;

import com.example.healthassistant.dto.WaterHistoryResponse;
import com.example.healthassistant.repository.WaterRepository;

import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.List;

@Service
public class WaterService {

    private final WaterRepository waterRepository;

    public WaterService(WaterRepository waterRepository) {
        this.waterRepository = waterRepository;
    }

    public int addWater(int amount) {

        if (amount <= 0) {
            throw new IllegalArgumentException(
                "Water amount must be greater than 0."
            );
        }

        LocalDate today = LocalDate.now();

        waterRepository.save(amount, today);

        return waterRepository.getTotalWater(today);
    }

    public int getTotalWater() {

        return waterRepository.getTotalWater(LocalDate.now());
    }

    public int getTotalWaterByDate(LocalDate date) {

        return waterRepository.getTotalWater(date);
    }

    public List<WaterHistoryResponse> getWaterHistory() {

        LocalDate today = LocalDate.now();

        LocalDate startDate = today.minusDays(6);

        return waterRepository.getWaterHistory(startDate, today);
    }
}

現在 Service 已經可以完成四種工作:

  1. 新增飲水紀錄
  2. 查詢今天的飲水量
  3. 查詢指定日期的飲水量
  4. 查詢最近七天的飲水紀錄

功能開始越來越完整了!


十一、修改 WaterController

接下來是今天最後一個主要步驟。

我們要在 Controller 加入兩個新的 GET API。

打開:

WaterController.java

加入:

@GetMapping("/date")
public ResponseEntity<Map<String, Object>> getWaterByDate(
        @RequestParam
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
        LocalDate date) {

    int total = waterService.getTotalWaterByDate(date);

    Map<String, Object> result = new HashMap<>();

    result.put("date", date);
    result.put("totalWater", total);
    result.put("unit", "ml");

    return ResponseEntity.ok(result);
}

這個 API 用來查詢指定日期。

接著加入:

@GetMapping("/history")
public ResponseEntity<List<WaterHistoryResponse>> getWaterHistory() {

    List<WaterHistoryResponse> history =
            waterService.getWaterHistory();

    return ResponseEntity.ok(history);
}

記得在 Controller 補上:

import java.time.LocalDate;
import java.util.List;

import org.springframework.format.annotation.DateTimeFormat;

import com.example.healthassistant.dto.WaterHistoryResponse;

這樣就完成了!


十二、使用 Postman 測試指定日期查詢

接下來重新啟動 Spring Boot。

先測試指定日期查詢。

HTTP Method:

GET

URL:

http://localhost:8080/health/water/date?date=2026-10-17

如果資料庫裡面有該日期的飲水紀錄,例如:

300 ml
500 ml
700 ml

預期回傳:

{
  "date": "2026-10-17",
  "totalWater": 1500,
  "unit": "ml"
}

如果查詢的日期沒有任何資料,則會回傳:

{
  "date": "2026-10-17",
  "totalWater": 0,
  "unit": "ml"
}

這樣就可以讓使用者查詢特定日期的飲水量。


十三、測試最近七天的歷史紀錄

接著測試:

GET http://localhost:8080/health/water/history

假設今天是 2026 年 10 月 17 日,而且資料庫有以下紀錄:

日期 飲水總量
10/13 1200 ml
10/14 1500 ml
10/15 1800 ml
10/16 1400 ml
10/17 1600 ml

預期回傳:

[
  {
    "date": "2026-10-13",
    "totalWater": 1200
  },
  {
    "date": "2026-10-14",
    "totalWater": 1500
  },
  {
    "date": "2026-10-15",
    "totalWater": 1800
  },
  {
    "date": "2026-10-16",
    "totalWater": 1400
  },
  {
    "date": "2026-10-17",
    "totalWater": 1600
  }
]

這裡有一個小細節。

雖然我們查詢的是最近七天,但目前 SQL 只會回傳資料庫裡有紀錄的日期。

如果某一天沒有飲水紀錄,就不會出現在結果中。

例如:

10/11 沒有紀錄
10/12 沒有紀錄

這兩天就不會出現在 JSON 裡面。

而且沒有紀錄不代表當天完全沒有喝水,只代表系統沒有保存到資料。

未來如果要製作完整的七天圖表,就可以再補上沒有紀錄的日期。


十四、今天的專案架構

經過今天的更新,專案架構大致如下:

health-assistant
│
├── src/main/java/com/example/healthassistant
│   │
│   ├── HealthAssistantApplication.java
│   │
│   ├── controller
│   │   ├── HealthController.java
│   │   └── WaterController.java
│   │
│   ├── service
│   │   ├── BmiService.java
│   │   └── WaterService.java
│   │
│   ├── repository
│   │   └── WaterRepository.java
│   │
│   ├── dto
│   │   ├── HealthRequest.java
│   │   ├── WaterRequest.java
│   │   └── WaterHistoryResponse.java
│   │
│   └── exception
│       └── GlobalExceptionHandler.java
│
├── src/main/resources
│   ├── application.properties
│   └── schema.sql
│
└── pom.xml

今天主要增加了:

WaterHistoryResponse.java

並修改:

WaterRepository.java
WaterService.java
WaterController.java

整個系統依然維持:

Controller
    ↓
Service
    ↓
Repository
    ↓
Database

這樣即使功能持續增加,程式也比較容易整理。


十五、今天學到的 SQL 語法

今天使用了幾個新的 SQL 語法。

整理如下:

SQL 語法 功能
SELECT 查詢資料
WHERE 設定查詢條件
BETWEEN 查詢指定範圍
SUM() 加總數值
GROUP BY 將資料分組
ORDER BY 排序查詢結果

其中我覺得最重要的是:

GROUP BY

因為我們的飲水紀錄是一次一次新增的。

例如:

10/17 09:00 → 300 ml
10/17 12:00 → 500 ml
10/17 15:00 → 250 ml

但如果想知道當天總共喝了多少水,就需要將同一天的紀錄加總。

所以可以使用:

SELECT record_date, SUM(amount)
FROM water_records
GROUP BY record_date;

這樣就能把多筆資料整理成每日統計結果。


十六、歷史紀錄和 AI 有什麼關係?

今天的功能雖然還沒有真正使用 AI,但已經開始接近未來的健康趨勢分析了。

因為 AI 如果要整理一段時間的健康紀錄,首先就需要取得歷史資料。

例如:

[
  {
    "date": "2026-10-15",
    "totalWater": 1800
  },
  {
    "date": "2026-10-16",
    "totalWater": 1400
  },
  {
    "date": "2026-10-17",
    "totalWater": 1600
  }
]

未來可以先由 Java 計算:

平均飲水量
最高飲水量
最低飲水量

再讓 AI 協助將統計結果整理成容易閱讀的文字。

例如:

最近幾天的飲水紀錄有所變化,
其中 10/15 的紀錄量最高。

不過目前的資料只是使用者輸入的紀錄,不能直接代表實際攝取量,也不能單憑這些數字判斷是否符合個人健康需求。

所以未來 AI 的角色,會先以整理資料和提供一般資訊為主。


Day 16 小結

今天成功讓智慧健康助理加入歷史查詢功能!

原本只能查詢今天的飲水總量,現在已經可以:

  1. 查詢指定日期的飲水量
  2. 查詢最近七天的飲水紀錄
  3. 使用 LocalDate 處理日期
  4. 使用 @RequestParam 接收日期參數
  5. 使用 @DateTimeFormat 轉換日期格式
  6. 使用 SQL 的 GROUP BY 和 SUM() 統計資料
  7. 使用 JdbcTemplate.query() 取得多筆結果

目前飲水紀錄功能已經具備:

新增飲水紀錄
      ↓
儲存至 H2
      ↓
查詢當日總量
      ↓
查詢指定日期
      ↓
查詢最近七天

今天最大的收穫是:

資料庫不只是拿來保存資料,更重要的是能夠查詢、整理和分析資料。

雖然現在還沒有做出圖表,但已經有了可以用來分析的歷史紀錄。


明日預告

Day 17|從紀錄到統計!計算七天平均飲水量

今天已經成功查詢最近七天的飲水紀錄。

但如果只是回傳一大串 JSON,使用者可能還是不容易看出整體情況。

所以下一篇準備加入統計功能!

預計會學習:

  • 如何計算七天平均飲水量
  • 如何找出紀錄量最高與最低的日期
  • 使用 Java 處理歷史資料
  • 建立飲水量統計 API
  • 為未來的 AI 健康摘要準備統計資料

讓智慧健康助理不只可以查詢紀錄,也能開始整理出更有意義的資訊!


上一篇
使用 Spring JDBC 將飲水紀錄存進資料庫
下一篇
用 Java 分析七天飲水量
系列文
Java × AI:30天打造智慧健康助理 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言